【CloudFormation】Security Hubに集約した検出結果を整形して通知する仕組みを実装してみた
はじめに
最近Security Hubでリージョン集約機能がアップデートにより実装されました。
【アップデート】AWS Security Hub が検出結果のリージョン集約に対応しました | DevelopersIO
このアップデートからAWSのセキュリティサービスで検出した結果をSecurity Hubに集約することで、これまで利用リージョン分展開していた通知の仕組みを1つのリージョンだけで実装することができるようになりました。
【小ネタ】GuardDuty 通知は Security Hub 経由で行うとリージョン集約ができて便利 | DevelopersIO
こうしてSecurity Hubに集めて検出結果を通知できるようになったわけですが、今回はもう一歩踏み込んで整形したメッセージで通知する仕組みを実装していきます。
本ブログで実装する通知の仕組みは、ほぼ弊社が提供しているセキュアアカウントの実装を元にしています。手軽に通知設定したい・まるっとセキュアなアカウントが欲しい方はセキュアアカウントを利用頂くと幸せになれるかもしれません。セキュアアカウントについて詳細に知りたい方は是非以下のブログをご参照ください。
できるようになること
Security Hubに集約した検出結果をCloudFormationスタックを2つ(または3つ)展開するだけで、以下のような通知をメールやSlackで受け取れます。
メールの通知例
Slackの通知例
Teamsの通知例
構成
検出した結果を全てSecurity Hubに集約、Eventルールで各サービスの検出結果を取得してStepFunctionsで整形という流れになっています。
通知先としてはメールとSlack、Teamsを想定しています。セキュリティサービス(Security HubやGuardDuty)以外の部分は基本的に実装は全てCloudFormationで行います。共通実装は必須、どの通知先を利用するかは任意です。もちろん両方利用しても構いません。
また、今回Security Hubに集約して通知する検出結果は以下3つのサービスを想定しています。
- GuardDuty
- Security Hub(AWS 基礎セキュリティのベストプラクティス v1.0.0)
- IAM Access Analyzer
CloudFormationテンプレート
今回実装に利用するCloudFormationテンプレートです。(コード量が多いため折りたたんでいます)
共通機能は必須、通知先はお好みでご利用ください。
①共通機能(クリックすると展開されます)
AWSTemplateFormatVersion: "2010-09-09" Description: "Set security alert shaping" Parameters: Prefix: Type: String Default: cm GuardDutySeverities: Type: CommaDelimitedList Default: "LOW,MEDIUM,HIGH,CRITICAL" SecurityHubSeverities: Type: CommaDelimitedList Default: "LOW,MEDIUM,HIGH,CRITICAL,INFORMATIONAL" Resources: #整形用ステートマシン SecurityAlertShapingStateMachine: Type: AWS::StepFunctions::StateMachine Properties: Definition: { "Comment": "A description of my state machine", "StartAt": "Choice Service", "States": { "Choice Service": { "Type": "Choice", "Choices": [ { "Variable": "$.detail.findings[0].ProductName", "StringEquals": "GuardDuty", "Next": "GuardDuty Choice Severity", }, { "Variable": "$.detail.findings[0].ProductName", "StringEquals": "Security Hub", "Next": "Security Hub Choice Severity", }, { "Variable": "$.detail.findings[0].ProductName", "StringEquals": "IAM Access Analyzer", "Next": "AccessAnalyzer Sharping", }, ], "Default": "Fail", }, "Security Hub Choice Severity": { "Type": "Choice", "Choices": [ { "Or": [ { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "LOW", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "INFORMATIONAL", }, ], "Next": "Security Hub Severity Low", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "MEDIUM", "Next": "Security Hub Severity Middle", }, { "Or": [ { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "HIGH", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "CRITICAL", }, ], "Next": "Security Hub Severity High", }, ], "Default": "Security Hub Severity None", }, "Security Hub Severity None": { "Type": "Pass", "Next": "Security Hub Sharping", "Result": "不明", "ResultPath": "$.SeverityName", }, "Security Hub Severity Low": { "Type": "Pass", "Next": "Security Hub Sharping", "Result": "低", "ResultPath": "$.SeverityName", }, "Security Hub Severity Middle": { "Type": "Pass", "Next": "Security Hub Sharping", "Result": "中", "ResultPath": "$.SeverityName", }, "Security Hub Severity High": { "Type": "Pass", "Next": "Security Hub Sharping", "Result": "高", "ResultPath": "$.SeverityName", }, "GuardDuty Choice Severity": { "Type": "Choice", "Choices": [ { "Or": [ { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "LOW", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "INFORMATIONAL", }, ], "Next": "GuardDuty Severity Low", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "MEDIUM", "Next": "GuardDuty Severity Middle", }, { "Or": [ { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "HIGH", }, { "Variable": "$.detail.findings[0].Severity.Label", "StringEquals": "CRITICAL", }, ], "Next": "GuardDuty Severity High", }, ], "Default": "GuardDuty Severity None", }, "GuardDuty Severity None": { "Type": "Pass", "Next": "GuardDuty Sharping", "Result": "不明", "ResultPath": "$.SeverityName", }, "GuardDuty Severity Low": { "Type": "Pass", "Next": "GuardDuty Sharping", "Result": "低", "ResultPath": "$.SeverityName", }, "GuardDuty Severity Middle": { "Type": "Pass", "Next": "GuardDuty Sharping", "Result": "中", "ResultPath": "$.SeverityName", }, "GuardDuty Severity High": { "Type": "Pass", "Next": "GuardDuty Sharping", "Result": "高", "ResultPath": "$.SeverityName", }, "GuardDuty Sharping": { "Type": "Pass", "Next": "EventBridge PutEvents", "Parameters": { "Subject.$": "States.Format('緊急度: {} GuardDutyセキュリティアラート Account: {}', $.SeverityName, $.detail.findings[0].AwsAccountId)", "Message.$": "States.Format('以下の脅威を検知しました。 \n意図したものであるか確認してください。 \n検出タイプ: {} \n詳細: {} \nリージョン: {} \n推奨事項URL: https://docs.aws.amazon.com/ja_jp/guardduty/latest/ug/guardduty_finding-types-active.html \nコンソールURL: {}', $.detail.findings[0].Types[0], $.detail.findings[0].Description, $.detail.findings[0].Region, $.detail.findings[0].SourceUrl)", }, }, "Security Hub Sharping": { "Type": "Pass", "Parameters": { "Subject.$": "States.Format('緊急度: {} Security Hubセキュリティアラート Account: {}', $.SeverityName, $.account)", "Message.$": "States.Format('Security Hubでセキュリティ上好ましくない設定を検知しました。 \n意図したものであるか確認してください。 \n検知内容: {} \nリソース種類: {} \nリソースID: {} \n詳細: {} \nリージョン: {} \n推奨事項URL: {} \nコンソールURL: https://{}.console.aws.amazon.com/securityhub/home?region={}#/standards/aws-foundational-security-best-practices-1.0.0/{}', $.detail.findings[0].Title, $.detail.findings[0].Resources[0].Type, $.detail.findings[0].Resources[0].Id, $.detail.findings[0].Description,$.detail.findings[0].Region, $.detail.findings[0].ProductFields.RecommendationUrl, $.detail.findings[0].Region, $.detail.findings[0].Region, $.detail.findings[0].ProductFields.ControlId)", }, "Next": "EventBridge PutEvents", }, "AccessAnalyzer Sharping": { "Type": "Pass", "Parameters": { "Subject.$": "States.Format('IAM Access Analyzerセキュリティアラート Account: {}', $.detail.findings[0].AwsAccountId)", "Message.$": "States.Format('以下リソースが外部共有されています。 \n意図した設定か確認してください。 \nリソース種類: {} \nリソース名: {} \nリージョン: {} \n詳細: {} \nコンソールURL: {}', $.detail.findings[0].Resources[0].Type, $.detail.findings[0].Resources[0].Id,$.detail.findings[0].Region,$.detail.findings[0].Description,$.detail.findings[0].SourceUrl)", }, "Next": "EventBridge PutEvents", }, "EventBridge PutEvents": { "Type": "Task", "Resource": "arn:aws:states:::events:putEvents", "Parameters": { "Entries": [ { "Detail": { "Subject.$": "$.Subject", "Message.$": "$.Message", }, "DetailType": "Sharped Findings", "EventBusName": !Ref SecurityAlertAggregatorBus, "Source": "custom.securityalert.stepfunctions", }, ], }, "End": True, }, "Fail": { "Type": "Fail" }, }, } RoleArn: !GetAtt SecurityAlertShapingStateMachineRole.Arn StateMachineName: !Sub ${Prefix}-sharping-security-alert-machine #整形用ステートマシン用ロール SecurityAlertShapingStateMachineRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "states.amazonaws.com" }, "Action": "sts:AssumeRole", }, ], } Policies: - PolicyName: !Sub ${Prefix}-shaping-statemachine-role-policy PolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["events:PutEvents"], "Resource": !GetAtt SecurityAlertAggregatorBus.Arn, }, ], } RoleName: !Sub ${Prefix}-shaping-statemachine-role #整形後の送信先イベントバス SecurityAlertAggregatorBus: Type: AWS::Events::EventBus Properties: Name: !Sub ${Prefix}-security-alert-aggregator-bus GuardDutyEventRule: Type: AWS::Events::Rule Properties: Name: !Sub ${Prefix}-security-alert-guardduty-finding-rule EventPattern: source: ["aws.securityhub"] detail-type: ["Security Hub Findings - Imported"] detail: findings: ProductName: ["GuardDuty"] Severity: Label: !Ref GuardDutySeverities RecordState: - "ACTIVE" Workflow: Status: - "NEW" Targets: - Arn: !GetAtt SecurityAlertShapingStateMachine.Arn Id: !Sub ${Prefix}-security-alert-guardduty-finding-rule RoleArn: !GetAtt SecurityAlertEventRuleRole.Arn SecurityHubEventRule: Type: AWS::Events::Rule Properties: Name: !Sub ${Prefix}-security-alert-securityhub-finding-rule EventPattern: source: - "aws.securityhub" detail-type: - "Security Hub Findings - Imported" detail: findings: Compliance: Status: - "FAILED" - "WARNING" - "NOT_AVAILABLE" RecordState: - "ACTIVE" Workflow: Status: - "NEW" ProductFields: StandardsArn: - "arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0" Severity: Label: !Ref SecurityHubSeverities Targets: - Arn: !GetAtt SecurityAlertShapingStateMachine.Arn Id: !Sub ${Prefix}-security-alert-securityhub-finding-rule RoleArn: !GetAtt SecurityAlertEventRuleRole.Arn AccessAnalyzerEventRule: Type: AWS::Events::Rule Properties: Name: !Sub ${Prefix}-security-alert-access-analyzer-finding-rule EventPattern: source: ["aws.securityhub"] detail-type: ["Security Hub Findings - Imported"] detail: findings: ProductName: ["IAM Access Analyzer"] RecordState: - "ACTIVE" Workflow: Status: - "NEW" Targets: - Arn: !GetAtt SecurityAlertShapingStateMachine.Arn Id: !Sub ${Prefix}-security-alert-access-analyzer-finding-rule RoleArn: !GetAtt SecurityAlertEventRuleRole.Arn #Eventルール用ロール SecurityAlertEventRuleRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "events.amazonaws.com" }, "Action": "sts:AssumeRole", }, ], } Policies: - PolicyName: !Sub ${Prefix}-security-alert-eventrule-role-policy PolicyDocument: { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["states:StartExecution"], "Resource": [!GetAtt SecurityAlertShapingStateMachine.Arn], }, ], } RoleName: !Sub ${Prefix}-security-alert-eventrule-role-policy Outputs: SecurityAlertAggregatorBus: Value: !Ref SecurityAlertAggregatorBus Export: Name: security-alert-aggregator-bus
②メール通知用(クリックすると展開されます)
AWSTemplateFormatVersion: 2010-09-09 Description: "Set security alert mail" Parameters: Prefix: Type: String Default: cm MailAddress: Description: Enter email address to send alert. Type: String Resources: AlertEventRuleRole: Type: "AWS::IAM::Role" Properties: RoleName: !Sub ${Prefix}-security-alert-mail-eventrule-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "events.amazonaws.com" Action: - "sts:AssumeRole" AlertEventRulePolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: !Sub ${Prefix}-security-alert-mail-eventrule-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "states:StartExecution" Resource: !Ref SendAlertMailMachine Roles: - !Ref AlertEventRuleRole AlertStateMachineRole: Type: "AWS::IAM::Role" Properties: RoleName: !Sub ${Prefix}-security-alert-mail-statemachine-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "states.amazonaws.com" Action: - "sts:AssumeRole" AlertStateMachinePolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: !Sub ${Prefix}-security-alert-mail-statemachine-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "sns:Publish" Resource: !Ref SNSTopic Roles: - !Ref AlertStateMachineRole EventRule: Type: "AWS::Events::Rule" Properties: Name: !Sub ${Prefix}-security-alert-mail-rule EventBusName: !ImportValue security-alert-aggregator-bus Description: "Security alert" EventPattern: { "source": ["custom.securityalert.stepfunctions"] } Targets: - Arn: !Ref SendAlertMailMachine Id: IdSendAlertMailMachine RoleArn: !GetAtt AlertEventRuleRole.Arn SendAlertMailMachine: Type: AWS::StepFunctions::StateMachine Properties: StateMachineName: !Sub ${Prefix}-security-alert-mail-machine Definition: Comment: "Send Security alert mail machine" StartAt: SNSPublish States: SNSPublish: Type: Task Resource: "arn:aws:states:::sns:publish" Parameters: TopicArn: !Ref SNSTopic Subject.$: "$.detail.Subject" Message.$: "$.detail.Message" End: true RoleArn: !GetAtt AlertStateMachineRole.Arn SNSTopic: Type: "AWS::SNS::Topic" Properties: TopicName: !Sub ${Prefix}-security-alert-mail-topic KmsMasterKeyId: alias/aws/sns SNSSubscription: Type: "AWS::SNS::Subscription" Properties: Endpoint: !Ref MailAddress Protocol: email TopicArn: !Ref SNSTopic SNSTopicPolicy: Type: "AWS::SNS::TopicPolicy" Properties: PolicyDocument: Id: default_policy_ID Version: "2012-10-17" Statement: - Sid: default_statement_ID Effect: Allow Principal: AWS: "*" Action: - "SNS:GetTopicAttributes" - "SNS:SetTopicAttributes" - "SNS:AddPermission" - "SNS:RemovePermission" - "SNS:DeleteTopic" - "SNS:Subscribe" - "SNS:ListSubscriptionsByTopic" - "SNS:Publish" - "SNS:Receive" Resource: !Ref SNSTopic Condition: StringEquals: "AWS:SourceOwner": !Ref "AWS::AccountId" - Sid: AllowPublishFromStepFunctions Effect: Allow Principal: AWS: !GetAtt AlertStateMachineRole.Arn Action: "sns:Publish" Resource: !Ref SNSTopic Topics: - !Ref SNSTopic
③Slack通知用(クリックすると展開されます)
AWSTemplateFormatVersion: 2010-09-09 Description: "Set security alert slack" Parameters: Prefix: Type: String Default: cm SlackChannelId: Description: Enter Slack Channel to send alert. Type: String BotUserOAuthToken: Description: Enter token for bot authentication. Type: String Resources: AlertEventRuleRole: Type: "AWS::IAM::Role" Properties: RoleName: !Sub ${Prefix}-security-alert-slack-eventrule-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "events.amazonaws.com" Action: - "sts:AssumeRole" AlertEventRulePolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: !Sub ${Prefix}-security-alert-slack-eventrule-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "events:InvokeApiDestination" Resource: !GetAtt EventApiDestination.Arn Roles: - !Ref AlertEventRuleRole EventRule: Type: AWS::Events::Rule Properties: Name: !Sub ${Prefix}-security-alert-slack-rule EventBusName: !ImportValue security-alert-aggregator-bus Description: "Security alert" EventPattern: { "source": ["custom.securityalert.stepfunctions"] } Targets: - Arn: !GetAtt EventApiDestination.Arn Id: IdSendAlertSlackApi RoleArn: !GetAtt AlertEventRuleRole.Arn InputTransformer: InputPathsMap: Message: "$.detail.Message" Subject: "$.detail.Subject" InputTemplate: !Sub '{"channel":"${SlackChannelId}","text":"<Subject>","blocks":[{"type":"header","text":{"type":"plain_text","text":"<Subject>"}},{"type":"divider"},{"type":"section","text":{"type":"mrkdwn","text":"<Message>"}}]}' EventApiDestination: Type: AWS::Events::ApiDestination Properties: Description: "Security alert ApiDestination" ConnectionArn: !GetAtt EventConnection.Arn HttpMethod: POST InvocationEndpoint: "https://slack.com/api/chat.postMessage" InvocationRateLimitPerSecond: 300 Name: !Sub ${Prefix}-security-alert-slack-apidestination EventConnection: Type: AWS::Events::Connection Properties: AuthorizationType: API_KEY Description: "Security alert Connection" Name: !Sub ${Prefix}-security-alert-slack-connection AuthParameters: ApiKeyAuthParameters: ApiKeyName: Authorization ApiKeyValue: !Sub Bearer ${BotUserOAuthToken}
④Teams通知用(クリックすると展開されます)
AWSTemplateFormatVersion: 2010-09-09 Description: "Set security alert teams" Parameters: Prefix: Type: String Default: cm TeamsWebhookUrl: Description: Enter Teams Channel to send alert. Type: String Resources: AlertEventRuleRole: Type: "AWS::IAM::Role" Properties: RoleName: !Sub ${Prefix}-security-alert-teams-eventrule-role AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Principal: Service: - "events.amazonaws.com" Action: - "sts:AssumeRole" AlertEventRulePolicy: Type: AWS::IAM::ManagedPolicy Properties: ManagedPolicyName: !Sub ${Prefix}-security-alert-teams-eventrule-policy PolicyDocument: Version: "2012-10-17" Statement: - Effect: "Allow" Action: - "events:InvokeApiDestination" Resource: !GetAtt EventApiDestination.Arn Roles: - !Ref AlertEventRuleRole EventRule: Type: AWS::Events::Rule Properties: Name: !Sub ${Prefix}-security-alert-teams-rule EventBusName: !ImportValue security-alert-aggregator-bus Description: "Security alert" EventPattern: { "source": ["custom.securityalert.stepfunctions"] } Targets: - Arn: !GetAtt EventApiDestination.Arn Id: IdSendAlertSlackApi RoleArn: !GetAtt AlertEventRuleRole.Arn InputTransformer: InputPathsMap: Message: "$.detail.Message" Subject: "$.detail.Subject" InputTemplate: !Sub '{"title": "<Subject>","text": "<Message>"}' EventApiDestination: Type: AWS::Events::ApiDestination Properties: Description: "Security alert ApiDestination" ConnectionArn: !GetAtt EventConnection.Arn HttpMethod: POST InvocationEndpoint: !Ref TeamsWebhookUrl InvocationRateLimitPerSecond: 300 Name: !Sub ${Prefix}-security-alert-teams-apidestination EventConnection: Type: AWS::Events::Connection Properties: AuthorizationType: API_KEY Description: "Security alert Connection" Name: !Sub ${Prefix}-security-alert-teams-connection AuthParameters: # 本来は必要ないが、必須項目のためダミーの値を入力 ApiKeyAuthParameters: ApiKeyName: key ApiKeyValue: value
実装してみる
先ほど紹介したCloudFormationを使って通知の仕組みを実装してみます。
前提
以下が設定されていることを前提としています。
- Security Hubを有効化していること
- GuardDuty、IAM Access Analyzerは任意
- Security Hubのリージョン集約機能を有効にしていること
①共通機能の展開
まずは各サービスのイベント取得と整形する機能を展開します。Security Hubの集約先のリージョンに ①共通機能 のテンプレートを使ってスタックを作成しましょう。パラメータは利用環境に合わせて変更してください。
- Prefix
- 各リソースにつけるプレフィックス
- デフォルトは
cm
- GuardDutySeverities
- GuardDutyで通知する重要度のラベル
LOW,MEDIUM,HIGH,CRITICAL
から通知したい重要度を入力- デフォルトは全て通知
- SecurityHubSeverities
- Security Hubで通知する重要度のラベル
LOW,MEDIUM,HIGH,CRITICAL,INFORMATIONAL
から通知したい重要度を入力- デフォルトは全て通知
その他はデフォルトで作成して大丈夫です。スタックの作成が完了することを確認しましょう。
実装している内容を以下で簡単に説明していますが、とりあえず使えればいいという方は ②メール通知用の展開 まで読み飛ばしてください。
EventBridge ルール
Security Hubに集約された検出結果から、目的のサービスだけを取得するEventルールを作成しています。
IAM Access Analyzerの場合は、Security Hubからイベントを取得するようなイベントパターンを設定しています。
{ "detail-type": ["Security Hub Findings - Imported"], "source": ["aws.securityhub"], "detail": { "findings": { "ProductName": ["IAM Access Analyzer"], "RecordState": ["ACTIVE"], "Workflow": { "Status": ["NEW"] } } } }
GuardDutyは重要度のフィルタリングをするためにSeverityのラベルを追加しています。${GuardDutySeverities}
はCloudFormationのインプットです。
{ "detail-type": ["Security Hub Findings - Imported"], "source": ["aws.securityhub"], "detail": { "findings": { "ProductName": ["GuardDuty"], "RecordState": ["ACTIVE"], "Workflow": { "Status": ["NEW"] }, "Severity": { "Label": ["${GuardDutySeverities}"] } } } }
Security Hubの通知はAWS 基礎セキュリティのベストプラクティス v1.0.0のみ通知させたかったので、以下のようなパターンで設定しました。${SecurityHubSeverities}
はCloudFormationのインプットです。
{ "detail-type": ["Security Hub Findings - Imported"], "source": ["aws.securityhub"], "detail": { "findings": { "Compliance": { "Status": ["FAILED", "WARNING", "NOT_AVAILABLE"] }, "RecordState": ["ACTIVE"], "ProductFields": { "StandardsArn": ["arn:aws:securityhub:::standards/aws-foundational-security-best-practices/v/1.0.0"] }, "Workflow": { "Status": ["NEW"] }, "Severity": { "Label": ["${SecurityHubSeverities}"] } } } }
より具体的にフィルタリングの内容を知りたい方はこちらのブログが分かりやすく解説されています。
StepFunctions
Security Hubから取得したイベントを整形するステートマシンの定義です。見づらくてすみません。
ステートマシンの詳細な定義は細かい話になるので、できればCloudFormationテンプレートを確認して下さい。 上から簡単に説明すると各サービスごとに整形が異なるため、まず取得したイベントがどのサービスかで分岐してます。その後GuardDutyとSecurity Hubでは重要度のラベルから[高,中,低]の判別をして次のフローに渡しています。
あとは各サービスごとに整形、カスタムイベントバスに整形したタイトルと本文をプッシュしてます。これらは全てLambdaを使わずStepFunctionsのみで実装できます、すごいですね。
カスタムイベントバスに集約されたイベントはメールやSlackへ通知されるのですが、通知先が環境によって異なるため①共通機能には含めていません。通知の実装は後述します。
②メール通知用の展開
メールで通知したい人向けです。他の通知先を利用する方は呼ばして頂いて構いません。
赤枠で囲った部分を設定していきます。
といっても②メール通知用のテンプレートでスタックを同じリージョンに作成するだけです。インプットは以下の通り。
- Prefix
- 各リソースにつけるプレフィックス
- デフォルトは
cm
- MailAddress
- 通知先のメールアドレス
上記のパラメータを入力したらそのままスタックを作成してください。
入力したメールアドレス宛にAmazon SNSからメールアドレスの登録確認として「AWS Notification - Subscription Confirmation」というタイトルのメールが届きます。本文中の「Confirm subscription」リンクを押して登録を完了してください。
これでメール通知設定は完了です。
③Slack通知用の展開
Slackで通知したい人向けです。他の通知先を利用する方は呼ばして頂いて構いません。
赤枠で囲った部分を設定していきます。
こちらも③Slack通知用のテンプレートでスタックを同じリージョンに作成します。インプットは以下の通り。
- Prefix
- 各リソースにつけるプレフィックス
- デフォルトは
cm
- BotUserOAuthToken
- 連携するBotアプリの認証情報
- SlackChannelId
- 連携するBotアプリが追加されているチャンネルID
BotUserOAuthToken
とSlackChannelId
の取得方法については以下ブログをご参照ください。
スタックの作成が完了すればSlackの通知設定は完了です。
④Teams通知用の展開
Teamsで通知したい人向けです。他の通知先を利用する方は呼ばして頂いて構いません。
赤枠で囲った部分を設定していきます。
こちらも④Teams通知用のテンプレートでスタックを同じリージョンに作成します。インプットは以下の通り。
- Prefix
- 各リソースにつけるプレフィックス
- デフォルトは
cm
- TeamsWebhookUrl
- 連携するTeamsのWebhookUrl
TeamsWebhookUrl
の取得方法については以下ブログの「Teams側の準備」をご参照ください。
スタックの作成が完了すればTeamsの通知設定は完了です。
通知の確認
実装が完了したので、試しにGuardDutyの検出結果が通知されるか確認してみます。バージニア北部で検知した結果を東京リージョンに実装した仕組みで通知してみます。
サンプルイベントでもいいのですが、通知量がすごい数になるのが嫌なのでこちらを使って疑似的に検知してみます。少し時間がかかりますが、簡単にお試し検知ができるのでおすすめです。
無事各通知先で通知を受け取ることができました。
マルチアカウントで通知機能を実装する場合
もし本ブログの通知機能をマルチアカウントに実装したい場合は、StackSetsを使って①共通機能を展開するのがおすすめです。実はStackSetsで利用することを想定して、あえてCloudFormationテンプレートで作成しています。
StackSetsはコンソールから利用してもいいのですが、個人的にはCDKを使って管理するとものすごく簡単に展開先のリージョンやOUを指定できるので是非利用してみてください。(サンプルコードは以下ブログ参照)
複数アカウントにまるっと実装するなら以下の流れが良いかなと思っています。
- GuardDutyやSecurity HubをOrganizations統合で有効化
- 共通機能(①)の実装はStackSetsでまとめて展開
- 通知先の設定(②、③、④)のみ各AWSアカウントのユーザーが実施
1と2をあらかじめ自動化しておくことで、3の通知設定だけをユーザーが行うだけで済みます。ユーザーが通知設定する部分はService Catalogをうまく活用することで効率化できるかもしれません。
是非このあたりの仕組み作りに本ブログを含め役立てて頂ければ幸いです。
おわりに
Security Hubに集約した検出結果を通知する仕組みをCloudFormationでまるっと作ってみました。長々と書きましたが前提部分が満たせていればCloudFormationのスタック作るだけなので気軽に試してみてください。中身を理解しながらアレンジできる方は運用に合わせてメッセージを変えたり、検知対象のAWSサービスを追加したり自由にどうぞ。
逆に管理するのが大変そうだなと思った方は、冒頭に紹介したセキュアアカウントの利用をご検討頂ければ幸いです。
参考
- 【小ネタ】GuardDuty 通知は Security Hub 経由で行うとリージョン集約ができて便利 | DevelopersIO
- [安全なAWSセキュリティ運用ナレッジ2022]セキュアアカウントの使い方 | DevelopersIO
- 【アップデート】AWS Security Hub が検出結果のリージョン集約に対応しました | DevelopersIO
- Security Hub 検出結果のフィルタリング例 | DevelopersIO
- Slackチャンネルにメッセージを投稿できるSlackAppを作成する | DevelopersIO
- GuardDutyに「PenTest:S3/KaliLinux」を検知させるPythonスクリプト作った | DevelopersIO
- [AWS CDK]組織内に展開するStackSetsをコードで管理する | DevelopersIO
- 【Organizations】組織内の GuardDuty設定を試してみる | DevelopersIO
- [アップデート]Security Hubが AWS Organizations と統合!組織内セキュリティチェック環境を簡単にセットアップ/管理できるようになりました | DevelopersIO